/************************************************************************
* textbuffer.c
* voxelands - 3d voxel world sandbox game
* Copyright (C) Lisa 'darkrose' Milne 2016 <lisa@ltmnet.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program.  If not, see <http://www.gnu.org/licenses/>
************************************************************************/

#include "common.h"
#define _WM_EXPOSE_ALL
#include "wm.h"
#include "graphics.h"

#include <wctype.h>

static rendertext_t *textbuffer_get_rendertext(textbuffer_t *t, uint32_t page)
{
	rendertext_t *ft;

	ft = malloc(sizeof(rendertext_t));
	if (!ft)
		return NULL;

	ft->page = page;
	ft->state = 0;
	ft->vao = 0;
	ft->vbo[0] = 0;
	ft->vbo[1] = 0;
	array_init(&ft->v,ARRAY_TYPE_FLOAT);
	array_init(&ft->t,ARRAY_TYPE_FLOAT);

	array_push_ptr(&t->data,ft);

	return ft;
}

/* initialise a textbuffer */
void textbuffer_init(textbuffer_t *t, font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options)
{
	if (!t)
		return;

	t->mx = max_x;
	t->my = max_y;
	t->mc = max_c;
	t->options = options;
	t->str = NULL;
	t->length = 0;
	t->size = 0;
	t->x = x;
	t->y = y;
	t->nx = 0;
	t->ny = 0;
	t->cw = 0;
	t->ch = 0;
	t->ch = size;
	t->font = f;
	t->font_size = size;

	array_init(&t->data,ARRAY_TYPE_PTR);

	if (!t->mc)
		return;

	t->str = malloc(sizeof(uint32_t)*t->mc);
	if (!t->str)
		return;

	t->size = t->mc;
	t->str[0] = 0;
}

/* create and initialise a textbuffer */
textbuffer_t *textbuffer_create(font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options)
{
	textbuffer_t *t = malloc(sizeof(textbuffer_t));
	if (!t)
		return NULL;

	textbuffer_init(t,f,size,x,y,max_x,max_y, max_c, options);

	return t;
}

/* clear the current string from a textbuffer */
void textbuffer_clear(textbuffer_t *t)
{
	rendertext_t *rt;
	if (!t)
		return;

	if (!t->mc && t->str) {
		free(t->str);
		t->str = NULL;
		t->size = 0;
	}

	t->length = 0;
	t->nx = 0;
	t->ny = 0;
	t->cw = 0;
	t->ch = t->font_size;

	while ((rt = array_pop_ptr(&t->data))) {
		array_free(&rt->v,0);
		array_free(&rt->t,0);
		if (rt->state) {
			glDeleteBuffers(2, rt->vbo);
			glDeleteVertexArrays(1,&rt->vao);
		}

		free(rt);
	}
}

/* clear and free a textbuffer */
void textbuffer_free(textbuffer_t *t)
{
	if (!t)
		return;

	textbuffer_clear(t);
	array_free(&t->data,0);
	if (t->str)
		free(t->str);
	free(t);
}

/* adjust the settings of a textbuffer, and regen gl data if necessary */
int textbuffer_adjust(textbuffer_t *t, font_t *f, int size, float x, float y, int max_x, int max_y, int max_c, uint32_t options)
{
	uint32_t *str;
	uint32_t len;
	uint32_t i;
	uint8_t redo = 0;
	if (!t)
		return 1;

	if (f && f != t->font) {
		t->font = f;
		redo = 1;
	}

	t->x = x;
	t->y = y;

	if (t->mx != max_x) {
		t->mx = max_x;
		redo = 1;
	}

	if (t->my != max_y) {
		t->my = max_y;
		redo = 1;
	}

	if (t->mc != max_c) {
		if (t->mc > max_c)
			redo = 1;
		t->mc = max_c;
	}

	if (size != t->font_size) {
		t->font_size = size;
		redo = 1;
	}

	if (t->options != options) {
		if ((t->options&TB_OPT_NUMERIC) == 0 && (options&TB_OPT_NUMERIC) == TB_OPT_NUMERIC)
			redo = 1;
		t->options = options;
	}

	if (!redo)
		return 0;

	t->cw = 0;
	t->ch = t->font_size;

	str = t->str;
	len =  t->length;
	t->str = NULL;
	t->size = 0;
	t->length = 0;

	textbuffer_clear(t);

	if (!str)
		return 0;

	for (i=0; i<len; i++) {
		textbuffer_addchar(t,str[i]);
	}

	free(str);

	return 0;
}

/* add a string to a textbuffer */
int textbuffer_addstr(textbuffer_t *t, char* str)
{
	int r = 0;
	int i = 0;
	uint32_t ch;
	if (!t)
		return 1;

	while ((ch = utf8_nextchar(str,&i))) {
		r = textbuffer_addchar(t,ch);
		if (r)
			break;
	}

	return r;
}

/* add a single utf32 character to a textbuffer */
int textbuffer_addchar(textbuffer_t *t, uint32_t ch)
{
	rendertext_t *rt = NULL;
	fontchar_t *fc;
	uint32_t page;
	float sf;
	v2_t v;
	v2_t tc;
	float w;
	float h;
	if (!t)
		return 1;

	if ((t->options&TB_OPT_NUMERIC) == TB_OPT_NUMERIC && !iswdigit(ch))
		return -1;

	if (t->mc && t->length == t->mc)
		return -1;

	page = ch/256;

	if (t->data.length) {
		rt = array_get_ptr(&t->data,t->data.length-1);
		if (rt && rt->page != page)
			rt = NULL;
	}

	if (!rt)
		rt = textbuffer_get_rendertext(t,page);

	if (!rt)
		return 1;

	if (font_ttf_get_char(t->font,ch,&fc,&rt->glid))
		return 1;

	sf = (float)t->font_size/fc->h;

	w = sf*fc->w;
	h = sf*fc->h;

	if (t->mx && t->nx+w > t->mx) {
		t->nx = 0;
		t->ny += t->font_size;
		t->ch += t->font_size;
	}

	if (t->my && t->ny+h > t->my)
		return -1;

	v.x = t->nx;
	v.y = t->ny-h;
	tc.x = fc->x[0];
	tc.y = fc->y[1];
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);
	v.x = t->nx+w;
	v.y = t->ny-h;
	tc.x = fc->x[1];
	tc.y = fc->y[1];
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);
	v.x = t->nx;
	v.y = t->ny;
	tc.x = fc->x[0];
	tc.y = fc->y[0];
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);
	v.x = t->nx+w;
	v.y = t->ny-h;
	tc.x = fc->x[1];
	tc.y = fc->y[1];
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);
	v.x = t->nx+w;
	v.y = t->ny;
	tc.x = fc->x[1];
	tc.y = fc->y[0];
	array_push_v2t(&rt->v,&v);
	array_push_v2t(&rt->t,&tc);

	t->nx += (sf*fc->a);
	if (t->nx > t->cw)
		t->cw = t->nx;

	if (t->size <= t->length+2) {
		uint32_t *s;
		uint32_t nl = t->size+16;

		s = realloc(t->str,sizeof(uint32_t)*nl);
		if (!s)
			return 1;
		t->str = s;
		t->size = nl;
	}

	t->str[t->length++] = ch;
	t->str[t->length] = 0;

	return 0;
}

/* get the current draw dimensions for a textbuffer */
int textbuffer_get_dimensions(textbuffer_t *t, int sizes[2])
{
	if (!t)
		return 1;

	sizes[0] = t->cw;
	sizes[1] = t->ch;
	return 0;
}
